<!-- 地图位置选择器组件 license by http://eleadmin.com -->
<template>
  <el-dialog
    :visible="show"
    :title="title || t('ele.map.title')"
    :width="width"
    :show-close="showClose"
    :custom-class="dialogClass"
    :close-on-click-modal="closeOnClickModal"
    :close-on-press-escape="closeOnPressEscape"
    :destroy-on-close="destroyOnClose"
    :lock-scroll="lockScroll"
    :append-to-body="appendToBody"
    :modal-append-to-body="modalAppendToBody"
    :modal="modal"
    :top="top"
    @open="onOpen"
    @closed="onClosed"
    @update:visible="updateVisible"
  >
    <div v-loading="loading">
      <div class="ele-map-picker-header">
        <div class="ele-map-picker-header-search">
          <el-autocomplete
            v-model="suggestionKeywords"
            :fetch-suggestions="onSearch"
            :placeholder="searchPlaceholder || t('ele.map.placeholder')"
            :popper-class="searchPopClass"
            @select="onSelect"
            @blur="onSuggestionBlur"
          >
            <i class="el-icon-search el-input__icon" slot="suffix"></i>
            <template slot-scope="{ item }">
              <div
                class="ele-map-picker-poi-item ele-map-picker-suggestion-item"
              >
                <i class="ele-map-picker-poi-item-icon el-icon-search"></i>
                <div class="ele-map-picker-poi-item-title">{{ item.name }}</div>
                <div
                  v-if="item.address"
                  class="ele-map-picker-poi-item-address"
                >
                  {{ item.address }}
                </div>
              </div>
            </template>
          </el-autocomplete>
        </div>
        <el-button
          type="primary"
          icon="el-icon-check"
          class="ele-btn-icon"
          :loading="doneLoading"
          @click="onDone"
        >
          {{ okText || t('ele.map.ok') }}
        </el-button>
      </div>
      <div class="ele-map-picker-body">
        <div class="ele-map-picker-main">
          <div ref="mapEl" :style="{ height: height }"></div>
          <template v-if="searchType === 0">
            <i class="ele-map-picker-main-plus el-icon-plus"></i>
            <img :class="iconClass" :src="markerSrc" alt="" />
          </template>
        </div>
        <div
          class="ele-map-picker-poi-list"
          :style="{ height: height }"
          v-loading="poiLoading"
        >
          <div
            v-for="(poi, index) in poiData"
            :key="index"
            :class="['ele-map-picker-poi-item', { active: poi.selected }]"
            @click="onChoose(index)"
          >
            <i
              class="ele-map-picker-poi-item-icon el-icon-location-outline"
            ></i>
            <div class="ele-map-picker-poi-item-title">{{ poi.name }}</div>
            <div v-if="poi.address" class="ele-map-picker-poi-item-address">{{
              poi.address
            }}</div>
            <i class="el-icon-circle-check ele-map-picker-poi-item-check"></i>
          </div>
        </div>
      </div>
    </div>
  </el-dialog>
</template>

<script>
  import Locale from 'element-ui/lib/mixins/locale';
  import AMapLoader from '@amap/amap-jsapi-loader';

  export default {
    name: 'EleMapPicker',
    mixins: [Locale],
    emits: ['done', 'open', 'closed', 'update:show'],
    props: {
      // 是否显示, 支持.sync修饰
      show: Boolean,
      // 地图的高度
      height: {
        type: String,
        default: '450px'
      },
      // 地图默认中心点
      center: Array,
      // 地图初始缩放级别
      zoom: {
        type: Number,
        default: 11
      },
      // 地图选中后缩放级别
      chooseZoom: {
        type: Number,
        default: 17
      },
      // poi检索最大数量
      poiSize: {
        type: Number,
        default: 30
      },
      // poi检索兴趣点类别
      poiType: {
        type: String,
        default: ''
      },
      // poi检索关键字
      poiKeywords: {
        type: String,
        default: ''
      },
      // poi检索半径
      poiRadius: {
        type: Number,
        default: 1000
      },
      // 是否返回行政区
      needCity: Boolean,
      // 是否强制选择
      forceChoose: {
        type: Boolean,
        default: true
      },
      // 搜索建议的城市范围
      suggestionCity: {
        type: String,
        default: '全国'
      },
      // 地点检索类型, 0: POI检索, 1: 关键字检索
      searchType: {
        type: Number,
        default: 0,
        validator: (value) => {
          return [0, 1].includes(value);
        }
      },
      // 搜索建议框提示文字
      searchPlaceholder: String,
      // marker图标地址
      markerSrc: {
        type: String,
        default:
          'https://3gimg.qq.com/lightmap/components/locationPicker2/image/marker.png'
      },
      // 地图默认的key
      mapKey: {
        type: String,
        default: '006d995d433058322319fa797f2876f5'
      },
      // 地图默认版本号
      mapVersion: {
        type: String,
        default: '2.0'
      },
      // 地图风格
      mapStyle: String,
      // 弹窗的标题
      title: String,
      // 弹窗的宽度
      width: {
        type: String,
        default: '780px'
      },
      // Dialog 的自定义类名
      customClass: String,
      // 是否可以通过点击 modal 关闭 Dialog
      closeOnClickModal: {
        type: Boolean,
        default: true
      },
      // 是否可以通过按下 ESC 关闭 Dialog
      closeOnPressEscape: {
        type: Boolean,
        default: true
      },
      // 是否显示关闭按钮
      showClose: {
        type: Boolean,
        default: true
      },
      // 是否在 Dialog 出现时将 body 滚动锁定
      lockScroll: {
        type: Boolean,
        default: true
      },
      // Dialog 自身是否插入至 body 元素上
      appendToBody: Boolean,
      // 遮罩层是否插入至 body 元素上
      modalAppendToBody: {
        type: Boolean,
        default: true
      },
      // 是否需要遮罩层
      modal: {
        type: Boolean,
        default: true
      },
      // Dialog CSS 中的 margin-top 值
      top: String,
      // 关闭时销毁 Dialog 中的元素
      destroyOnClose: {
        type: Boolean,
        default: false
      },
      // 完成按钮文字
      okText: String,
      // 是否暗黑主题
      darkMode: Boolean
    },
    data() {
      return {
        // poi检索的数据
        poiData: [],
        // 搜索建议列表
        suggestionData: [],
        // 搜索输入内容
        suggestionKeywords: '',
        // 初始化loading
        loading: true,
        // poi检索loading
        poiLoading: false,
        // 确定按钮loading
        doneLoading: false,
        // 跳动图标的class
        iconClass: ['ele-map-picker-main-icon'],
        // 上次搜索输入内容
        lastSuggestion: '',
        // 选中的建议
        chooseSuggestion: null,
        // 是否是点击poi列表移动地图
        isSelMove: false,
        // 地图实例
        map: null,
        // poi检索实例
        placeSearch: null,
        // 搜索建议实例
        autoComplete: null,
        // 地图中心标注
        centerMarker: null
      };
    },
    computed: {
      // 弹窗的class
      dialogClass() {
        const classes = ['ele-map-picker-dialog'];
        if (this.customClass !== 0) {
          classes.push(this.customClass);
        }
        return classes.join(' ');
      },
      // 搜索建议pop的class
      searchPopClass() {
        const classes = ['ele-map-picker-search-pop'];
        if (this.searchType !== 0) {
          classes.push('ele-map-picker-hide');
        }
        return classes.join(' ');
      }
    },
    methods: {
      /* 弹窗打开回调 */
      onOpen() {
        this.renderMap(); // 渲染地图
        this.$emit('open');
      },
      /* 弹窗关闭回调 */
      onClosed() {
        if (this.destroyOnClose) {
          this.destroyAll();
        }
        this.$emit('closed');
      },
      /* 更新visible */
      updateVisible(value) {
        this.$emit('update:show', value);
      },
      /* poi列表选中 */
      onChoose(index) {
        this.isSelMove = true;
        this.setPoiSelected(index);
        const { lng, lat } = this.poiData[index].location;
        this.setMapCenter(lng, lat, this.chooseZoom);
        if (this.searchType !== 0) {
          this.showCenterMarker(lng, lat); // 非poi检索类型地图中心添加标记
        } else {
          this.bounceIcon(); // poi检索类型地图中心图标跳动
        }
      },
      /* 搜索建议 */
      onSearch(value, callback) {
        if (!value || this.lastSuggestion === value) {
          callback(this.suggestionData);
          return;
        }
        this.lastSuggestion = value;
        if (this.searchType !== 0) {
          this.poiLoading = true;
        }
        this.searchKeywords(value)
          .then((data) => {
            if (this.searchType === 0) {
              // POI检索模式
              this.suggestionData = data;
              callback(this.suggestionData);
            } else {
              // 关键字检索模式
              this.poiData = data;
              this.poiLoading = false;
              this.removeCenterMarker();
              callback([]);
            }
          })
          .catch((e) => {
            this.poiLoading = false;
            console.error(e);
          });
      },
      /* 搜索建议选中 */
      onSelect(item) {
        if (!this.poiData.length || this.poiData[0].name !== item.name) {
          this.poiData.unshift(Object.assign({}, item));
          this.setPoiSelected(0);
        }
        if (item.location) {
          this.setMapCenter(
            item.location.lng,
            item.location.lat,
            this.chooseZoom
          );
        }
        this.chooseSuggestion = item;
      },
      /* 搜索建议框失去焦点 */
      onSuggestionBlur() {
        if (this.searchType === 0) {
          this.suggestionKeyWords = '';
        }
      },
      /* 确定选择 */
      onDone() {
        const selected = this.poiData.find((d) => d.selected);
        if (!selected) {
          // 强制选择
          if (this.forceChoose) {
            this.$message.error(this.t('ele.map.message'));
            return;
          }
          // 未选择使用地图中心点
          this.doneLoading = true;
          this.getMapCenter(this.needCity)
            .then((result) => {
              this.doneLoading = false;
              this.$emit('done', result);
            })
            .catch((e) => {
              console.error(e);
              this.doneLoading = false;
              this.$emit('done', {});
            });
          return;
        }
        const location = Object.assign(
          {
            name: selected.name,
            address: selected.address || ''
          },
          selected.location
        );
        // 获取行政区
        if (this.needCity) {
          this.doneLoading = true;
          this.setMapCenter(location.lng, location.lat);
          this.getMapCenter(true)
            .then(({ city }) => {
              this.doneLoading = false;
              location.city = city;
              this.$emit('done', location);
            })
            .catch((e) => {
              console.error(e);
              this.doneLoading = false;
              this.$emit('done', location);
            });
        } else {
          this.$emit('done', location);
        }
      },
      /* 销毁地图 */
      destroyAll() {
        this.destroyMap();
        this.poiData = [];
        this.suggestionData = [];
        this.suggestionKeywords = '';
        this.lastSuggestion = '';
        this.chooseSuggestion = null;
      },
      /* 让图标跳动 */
      bounceIcon() {
        this.iconClass = [this.iconClass[0]];
        this.$nextTick(() => {
          setTimeout(() => {
            this.iconClass = [this.iconClass[0], 'ele-map-picker-anim-bounce'];
          }, 0);
        });
      },
      /* 设置poi列表选中 */
      setPoiSelected(index) {
        this.poiData.forEach((d, i) => {
          d.selected = index === i;
        });
      },
      /* poi检索 */
      searchPoi(lng, lat, force) {
        // 非POI检索模式
        if (!force && this.searchType !== 0) {
          this.poiLoading = false;
          return;
        }
        this.poiLoading = true;
        this.searchNearBy(lng, lat)
          .then((data) => {
            this.poiLoading = false;
            // 如果选中的搜索建议不在poi列表中则添加
            if (this.chooseSuggestion) {
              if (
                data.length === 0 ||
                data[0].name !== this.chooseSuggestion.name
              ) {
                this.poiData = [
                  Object.assign({ selected: true }, this.chooseSuggestion)
                ].concat(data);
              } else {
                this.poiData = data;
                this.poiData[0].selected = true;
              }
              this.chooseSuggestion = null;
            } else {
              this.poiData = data;
            }
          })
          .catch((e) => {
            this.poiLoading = false;
            console.error(e);
          });
      },
      /* 渲染地图 */
      renderMap() {
        if (this.map) {
          return;
        }
        AMapLoader.load({
          key: this.mapKey,
          version: this.mapVersion,
          plugins: ['AMap.PlaceSearch', 'AMap.AutoComplete']
        })
          .then((AMap) => {
            // 参数配置
            const option = {
              zoom: this.zoom, // 初缩放级别
              center: this.center, // 初始中心点
              resizeEnable: true // 监控地图容器尺寸变化
            };
            if (this.mapStyle) {
              option.mapStyle = this.mapStyle;
            } else if (this.darkMode) {
              option.mapStyle = 'amap://styles/dark';
            }
            this.map = new AMap.Map(this.$refs.mapEl, option);

            // 获取poi检索实例
            this.placeSearch = new AMap.PlaceSearch({
              type: this.poiType,
              pageSize: this.poiSize,
              pageIndex: 1
            });

            // 获取输入建议实例
            this.autoComplete = new AMap.AutoComplete({
              city: this.suggestionCity
            });

            // 地图中心marker
            this.centerMarker = new AMap.Marker({
              icon: new AMap.Icon({
                image: this.markerSrc,
                size: new AMap.Size(26, 36.5),
                imageSize: new AMap.Size(26, 36.5)
              }),
              offset: new AMap.Pixel(-13, -36.5)
            });

            // 地图加载完成事件
            this.map.on('complete', () => {
              const { lng, lat } = this.map.getCenter();
              this.loading = false;
              this.poiLoading = true;
              this.searchPoi(lng, lat, true);
            });

            // 地图移动结束事件
            this.map.on('moveend', () => {
              const { lng, lat } = this.map.getCenter();
              if (this.isSelMove) {
                this.isSelMove = false;
              } else {
                this.bounceIcon();
                this.searchPoi(lng, lat);
              }
            });
          })
          .catch((e) => {
            console.error(e);
          });
      },
      /* 设置地图中心 */
      setMapCenter(lng, lat, zoom) {
        if (this.map) {
          if (typeof zoom === 'undefined') {
            this.map.setCenter([lng, lat]);
          } else {
            this.map.setZoomAndCenter(zoom, [lng, lat]);
          }
        }
      },
      /* 获取地图中心 */
      getMapCenter(needCity) {
        return new Promise((resolve, reject) => {
          if (!this.map) {
            reject(new Error('map is null'));
            return;
          }
          const result = this.map.getCenter();
          if (needCity) {
            this.map.getCity((city) => {
              result.city = city;
              resolve(result);
            });
          } else {
            resolve(result);
          }
        });
      },
      /* 添加地图中心marker */
      showCenterMarker(lng, lat) {
        if (!this.map) {
          console.error('map is null');
          return;
        }
        if (!this.centerMarker) {
          console.error('centerMarker is null');
          return;
        }
        if (typeof lng !== 'undefined' && typeof lat !== 'undefined') {
          this.centerMarker.setPosition([lng, lat]);
          this.map.add(this.centerMarker);
        } else {
          this.removeCenterMarker();
        }
      },
      /* 移除地图中心marker */
      removeCenterMarker() {
        if (this.map && this.centerMarker) {
          this.map.remove(this.centerMarker);
        }
      },
      /* poi检索 */
      searchNearBy(lng, lat) {
        return new Promise((resolve, reject) => {
          if (!this.placeSearch) {
            reject(new Error('placeSearch is null'));
            return;
          }
          this.placeSearch.searchNearBy(
            this.poiKeywords,
            [lng, lat],
            this.poiRadius,
            (status, result) => {
              if (status === 'complete') {
                const data = result.poiList.pois.filter(
                  (p) => typeof p.location !== 'undefined'
                );
                resolve(data);
              } else {
                reject(new Error(status));
              }
            }
          );
        });
      },
      /* 关键字检索 */
      searchKeywords(value) {
        return new Promise((resolve, reject) => {
          if (!this.autoComplete) {
            reject(new Error('autoComplete is null'));
            return;
          }
          this.autoComplete.search(value, (status, result) => {
            if (result.tips) {
              resolve(
                result.tips.map((d) => {
                  return Object.assign({}, d, {
                    text: d.name,
                    value: d.name,
                    address: Array.isArray(d.address) ? d.address[0] : d.address
                  });
                })
              );
            } else {
              resolve([]);
            }
          });
        });
      },
      /* 切换地图主题 */
      changeMapStyle(mapStyle, darkMode) {
        if (this.map) {
          if (mapStyle) {
            this.map.setMapStyle(mapStyle);
          } else if (darkMode) {
            this.map.setMapStyle('amap://styles/dark');
          } else {
            this.map.setMapStyle('amap://styles/normal');
          }
        }
      },
      /* 销毁地图 */
      destroyMap() {
        if (this.map) {
          this.map.destroy();
          this.placeSearch = null;
          this.autoComplete = null;
          this.centerMarker = null;
          this.map = null;
        }
      }
    },
    watch: {
      darkMode(value) {
        if (!this.mapStyle) {
          this.changeMapStyle(null, value);
        }
      },
      mapStyle(value) {
        if (value) {
          this.changeMapStyle(value);
        }
      }
    },
    unmounted() {
      this.destroyAll();
    }
  };
</script>

<style lang="scss">
  .ele-map-picker-dialog .el-dialog__body {
    padding: 0;
  }

  .ele-map-picker-hide {
    display: none !important;
  }

  /* 顶部工具栏 */
  .ele-map-picker-header {
    display: flex;
    padding: 8px 18px;

    .ele-map-picker-header-search {
      flex: 1;

      .el-autocomplete {
        max-width: 200px;
      }
    }
  }

  /* 主体区域 */
  .ele-map-picker-body {
    display: flex;
    border-top: 1px solid hsla(0, 0%, 60%, 0.15);

    /* 地图容器 */
    .ele-map-picker-main {
      flex: 1;
      position: relative;
      border-bottom-left-radius: 4px;
      overflow: hidden;

      .ele-map-picker-main-plus {
        color: #3b74ff;
        font-size: 12px;
        font-weight: 600;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }

      .ele-map-picker-main-icon {
        width: 26px;
        position: absolute;
        left: 50%;
        bottom: 50%;
        margin-left: -13px;
      }
    }

    /* poi列表 */
    .ele-map-picker-poi-list {
      width: 300px;
      overflow: auto;
    }
  }

  /* poi列表item */
  .ele-map-picker-poi-item {
    padding: 8px 30px 8px 44px;
    border-bottom: 1px solid hsla(0, 0%, 60%, 0.15);
    position: relative;
    cursor: pointer;

    &:hover {
      background-color: hsla(0, 0%, 60%, 0.05);
    }

    .ele-map-picker-poi-item-icon {
      position: absolute;
      top: 50%;
      left: 14px;
      transform: translateY(-50%);
      font-size: 20px;
      opacity: 0.4;
    }

    .ele-map-picker-poi-item-title {
      font-size: 14px;
    }

    .ele-map-picker-poi-item-address {
      font-size: 12px;
      margin-top: 2px;
      opacity: 0.6;
    }

    .ele-map-picker-poi-item-check {
      position: absolute;
      top: 50%;
      right: 7px;
      transform: translateY(-50%);
      color: #3b74ff;
      font-size: 16px;
      display: none;
    }

    &.active .ele-map-picker-poi-item-check {
      display: block;
    }
  }

  /* 搜索建议列表 */
  .ele-map-picker-search-pop {
    max-width: 100%;
    width: 240px !important;

    .el-autocomplete-suggestion__wrap {
      max-height: 320px;
    }
  }

  .ele-map-picker-suggestion-item {
    padding-right: 0;
    padding-left: 24px;
    line-height: normal;
    border: none;

    &:hover {
      background-color: transparent;
    }

    .ele-map-picker-poi-item-icon {
      left: 0;
      font-size: 14px;
      font-weight: 600;
      opacity: 0.3;
    }

    .ele-map-picker-poi-item-title,
    .ele-map-picker-poi-item-address {
      margin: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      word-break: break-all;
    }
  }

  /* 地图图标跳动动画 */
  .ele-map-picker-anim-bounce {
    animation: elePickerAnimBounce 500ms;
    animation-direction: alternate;
  }

  @keyframes elePickerAnimBounce {
    0%,
    60%,
    75%,
    90%,
    to {
      transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
    }

    0%,
    to {
      transform: translate3d(0, 0, 0);
    }

    25% {
      transform: translate3d(0, -10px, 0);
    }

    50% {
      transform: translate3d(0, -20px, 0);
    }

    75% {
      transform: translate3d(0, -10px, 0);
    }
  }

  /* 小屏幕适应 */
  @media screen and (max-width: 768px) {
    .ele-map-picker-dialog {
      margin: 0 !important;
      width: 100vw !important;
      max-width: unset !important;
      border-radius: 0 !important;
    }

    .ele-map-picker-body {
      display: block;

      .ele-map-picker-main > div {
        height: 250px !important;
      }

      .ele-map-picker-poi-list {
        width: auto;
        height: calc(100vh - 353px) !important;
      }
    }
  }
</style>
